package tr.helper.repository;

import cn.hutool.core.util.ReflectUtil;
import com.google.common.collect.Lists;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import tr.helper.CommonUtil;

/**
 * tr-service
 *
 * @author MrYe
 * @date 2022-01-14 17:16
 */
public class BaseRepository<T> implements DataRepository<T> {

    public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";

    private Class<T> clazz;

    public BaseRepository() {
        ParameterizedType pt = (ParameterizedType) getClass().getGenericSuperclass();
        clazz = (Class<T>) pt.getActualTypeArguments()[0];
    }

    public Connection createConnection() {
        return new DataInitialization().createConnection();
    }

    @Override
    public int
    insertSelective(T model) {
        final Connection connection = createConnection();
        try {
            final Statement stmt = connection.createStatement();

            String tableName = Support.getTableName(clazz);

            StringBuilder sqlCol = new StringBuilder("(");
            StringBuilder sqlVal = new StringBuilder("(");
            boolean hasValue = false;
            final Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                if (Modifier.isStatic(field.getModifiers())) {
                    continue;
                }
                String fieldName = field.getName();
                final Object value = ReflectUtil.getFieldValue(model, fieldName);
                if (null != value) {
                    if (!hasValue) {
                        hasValue = true;
                    }
                    String type = field.getGenericType().toString();
                    sqlCol.append(String.format("%s,", fieldName));
                    if (Arrays.asList("class java.lang.Long", "class java.lang.Integer", "class java.lang.Boolean").contains(type)) {
                        sqlVal.append(String.format("%s,", value));
                    } else if ("class java.util.Date".equals(type)) {
                        sqlVal.append(String.format("'%s',", new SimpleDateFormat(DATE_FORMAT).format((java.util.Date) value)));
                    } else {
                        sqlVal.append(String.format("'%s',", value));
                    }
                }
            }
            if (hasValue) {
                String sql = String.format("insert into %s %s values %s", tableName, CommonUtil.trimEnd(sqlCol.toString(), ",") + ")"
                    , CommonUtil.trimEnd(sqlVal.toString(), ",") + ")");
                CommonUtil.log("insertSelective sql:%s", sql);

                stmt.execute(sql);
                stmt.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return 0;
    }

    @Override
    public int deleteByPrimaryKey(Long id) {
        final Connection connection = createConnection();
        try {
            String tableName = Support.getTableName(clazz);
            final String sql = String.format("delete from %s where id=%s", tableName, id);
            final Statement stmt = connection.createStatement();
            stmt.execute(sql);
            stmt.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return 0;
    }

    @Override
    public int updateByPrimaryKey(T model, Long id) {
        final Connection connection = createConnection();
        try {
            final Statement stmt = connection.createStatement();

            String tableName = Support.getTableName(clazz);

            StringBuilder sqlUpd = new StringBuilder();
            boolean hasValue = false;
            final Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                if (Modifier.isStatic(field.getModifiers())) {
                    continue;
                }
                String fieldName = field.getName();
                final Object value = ReflectUtil.getFieldValue(model, fieldName);
                if (null != value) {
                    if (!hasValue) {
                        hasValue = true;
                    }
                    String type = field.getGenericType().toString();
                    if (Arrays.asList("class java.lang.Long", "class java.lang.Integer", "class java.lang.Boolean").contains(type)) {
                        sqlUpd.append(String.format("%s=%s,", fieldName, value));
                    } else if ("class java.util.Date".equals(type)) {
                        sqlUpd.append(String.format("%s='%s',", fieldName, new SimpleDateFormat(DATE_FORMAT).format((java.util.Date) value)));
                    } else {
                        sqlUpd.append(String.format("%s='%s',", fieldName, value));
                    }
                }
            }
            if (hasValue) {
                String sql = String.format("update %s set %s where id=%s", tableName, CommonUtil.trimEnd(sqlUpd.toString(), ","), id);
                CommonUtil.log("updateByPrimaryKey sql:%s", sql);
                stmt.execute(sql);
                stmt.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return 0;
    }

    @Override
    public T select(T model) {
        final Connection connection = createConnection();
        try {
            final Statement queryStmt = connection.createStatement();
            String tableName = Support.getTableName(clazz);
            final String sql = getSqlWithCond(clazz, model, "select * from " + tableName) + " limit 1";
            ResultSet rs = queryStmt.executeQuery(sql);
            T instance = (T) clazz.newInstance();
            while (rs.next()) {
                final Field[] fields = clazz.getDeclaredFields();
                for (Field field : fields) {
                    if (Modifier.isStatic(field.getModifiers())) {
                        continue;
                    }
                    String type = field.getGenericType().toString();
                    if ("class java.lang.String".equals(type)) {
                        ReflectUtil.setFieldValue(instance, field, rs.getString(field.getName()));
                    } else if ("class java.lang.Long".equals(type)) {
                        ReflectUtil.setFieldValue(instance, field, rs.getLong(field.getName()));
                    } else if ("class java.lang.Integer".equals(type)) {
                        ReflectUtil.setFieldValue(instance, field, rs.getInt(field.getName()));
                    } else if ("class java.math.BigDecimal".equals(type)) {
                        ReflectUtil.setFieldValue(instance, field, rs.getBigDecimal(field.getName()));
                    } else if ("class java.util.Date".equals(type)) {
                        final String strDate = rs.getString(field.getName());
                        if (StringUtils.isNotBlank(strDate)) {
                            ReflectUtil.setFieldValue(instance, field, new SimpleDateFormat(DATE_FORMAT).parse(strDate));
                        }
                    }
                }
            }
            queryStmt.close();
            return instance;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public List<T> selectAll(T model) {
        final Connection connection = createConnection();
        try {
            final Statement queryStmt = connection.createStatement();
            String tableName = Support.getTableName(clazz);
            final String searchSql = "select * from " + tableName;
            ResultSet rs = queryStmt.executeQuery(getSqlWithCond(clazz, model, searchSql));
            List<T> list = Lists.newArrayList();
            while (rs.next()) {
                T instance = clazz.newInstance();
                final Field[] fields = clazz.getDeclaredFields();
                for (Field field : fields) {
                    if (Modifier.isStatic(field.getModifiers())) {
                        continue;
                    }
                    String type = field.getGenericType().toString();
                    if ("class java.lang.String".equals(type)) {
                        ReflectUtil.setFieldValue(instance, field, rs.getString(field.getName()));
                    } else if ("class java.lang.Long".equals(type)) {
                        ReflectUtil.setFieldValue(instance, field, rs.getLong(field.getName()));
                    } else if ("class java.lang.Integer".equals(type)) {
                        ReflectUtil.setFieldValue(instance, field, rs.getInt(field.getName()));
                    } else if ("class java.math.BigDecimal".equals(type)) {
                        ReflectUtil.setFieldValue(instance, field, rs.getBigDecimal(field.getName()));
                    } else if ("class java.util.Date".equals(type)) {
                        final String strDate = rs.getString(field.getName());
                        if (StringUtils.isNotBlank(strDate)) {
                            ReflectUtil.setFieldValue(instance, field, new SimpleDateFormat(DATE_FORMAT).parse(strDate));
                        }
                    }
                }
                list.add(instance);
            }
            queryStmt.close();
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Collections.emptyList();
    }

    public String getSqlWithCond(Class<?> clazz, T model, final String searchSql) {
        if (null == model) {
            return searchSql;
        }
        StringBuilder cond = new StringBuilder();
        final Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (Modifier.isStatic(field.getModifiers())) {
                continue;
            }
            Object value = ReflectUtil.getFieldValue(model, field.getName());
            if (null != value) {
                String type = field.getGenericType().toString();
                if ("class java.lang.String".equals(type)) {
                    cond.append(String.format("%s='%s'", field.getName(), value));
                } else if ("class java.lang.Integer".equals(type) || "class java.lang.Long".equals(type)) {
                    cond.append(String.format("%s=%s", field.getName(), value));
                } else if (type.startsWith("java.util.List")) {
                    cond.append(String.format("%s in(%s)", field.getName(), StringUtils.join(((List) value), ",")));
                }
            }
        }
        if (StringUtils.length(cond.toString()) > 0) {
            return searchSql + " where " + cond.toString();
        }
        return searchSql;
    }

}
